All files / util obj_map.ts

100% Statements 52/52
100% Branches 14/14
100% Functions 9/9
100% Lines 45/45
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111                                  2x                   2x             1702x       1702x     2x 4066x 4066x 4066x 1320x   2755x 2755x 2730x     16x     2x 1007x       2x 1506x 1506x 1506x 1277x 1277x   229x 230x 222x 222x     7x           2x 717x 717x 717x 1x   716x 718x 715x 708x   7x   715x     1x     2x 3197x 2143x 2143x         2x 58x   2x  
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { Equatable } from './misc';
import * as objUtil from './obj';
 
type Entry<K, V> = [K, V];
 
/**
 * A map implementation that uses objects as keys. Objects must implement the
 * Equatable interface and must be immutable. Entries in the map are stored
 * together with the key being produced from the mapKeyFn. This map
 * automatically handles collisions of keys.
 */
export class ObjectMap<KeyType extends Equatable<KeyType>, ValueType> {
  /**
   * The inner map for a key -> value pair. Due to the possibility of
   * collisions we keep a list of entries that we do a linear search through
   * to find an actual match. Note that collisions should be rare, so we still
   * expect near constant time lookups in practice.
   */
  private inner: {
    [canonicalId: string]: Array<Entry<KeyType, ValueType>>;
  } = {};
 
  constructor(private mapKeyFn: (key: KeyType) => string) {}
 
  /** Get a value for this key, or undefined if it does not exist. */
  get(key: KeyType): ValueType | undefined {
    const id = this.mapKeyFn(key);
    const matches = this.inner[id];
    if (matches === undefined) {
      return undefined;
    }
    for (const [otherKey, value] of matches) {
      if (otherKey.isEqual(key)) {
        return value;
      }
    }
    return undefined;
  }
 
  has(key: KeyType): boolean {
    return this.get(key) !== undefined;
  }
 
  /** Put this key and value in the map. */
  set(key: KeyType, value: ValueType): void {
    const id = this.mapKeyFn(key);
    const matches = this.inner[id];
    if (matches === undefined) {
      this.inner[id] = [[key, value]];
      return;
    }
    for (let i = 0; i < matches.length; i++) {
      if (matches[i][0].isEqual(key)) {
        matches[i] = [key, value];
        return;
      }
    }
    matches.push([key, value]);
  }
 
  /**
   * Remove this key from the map. Returns a boolean if anything was deleted.
   */
  delete(key: KeyType): boolean {
    const id = this.mapKeyFn(key);
    const matches = this.inner[id];
    if (matches === undefined) {
      return false;
    }
    for (let i = 0; i < matches.length; i++) {
      if (matches[i][0].isEqual(key)) {
        if (matches.length === 1) {
          delete this.inner[id];
        } else {
          matches.splice(i, 1);
        }
        return true;
      }
    }
    return false;
  }
 
  forEach(fn: (key: KeyType, val: ValueType) => void): void {
    objUtil.forEach(this.inner, (_, entries) => {
      for (const [k, v] of entries) {
        fn(k, v);
      }
    });
  }
 
  isEmpty(): boolean {
    return objUtil.isEmpty(this.inner);
  }
}